#pragma once

#include <string>
#include <vector>
#include "../database.hpp"
#include "../except.hpp"
#include "user.hpp"

namespace pharmacy_server
{
    class UserModel
    {
    public:
        UserModel()
            : _current_user(nullptr)
        {
            try
            {
                easymysql::result res = easymysql::executer(database::instance())("SELECT username, password, role FROM users");

                User::tuple_of_user line;
                while (res.get<User::tuple_of_user>(&line))
                {
                    _users.push_back(User::from_tuple_quickly(line));
                }
            }
            catch (const easymysql::except& e)
            {
                easylog::error("load users failed exception: {}", e.what());
                throw model_except("load users failed exception: " + std::string(e.what()));
            }
        }

        bool create_user(const std::string& username, const std::string& password, const std::string& role)
        {
            try
            {
                easymysql::executer(database::instance())(R"(
INSERT INTO users (username, password, role) VALUES ('{}', '{}', '{}')
                )", username, password, role);

                _users.push_back(User(username, password, role));
            }
            catch (const easymysql::except& e)
            {
                easylog::error("create user failed exception: {}", e.what());
                throw model_except("create user failed exception: " + std::string(e.what()));
                return false;
            }

            return true;
        }

        bool delete_user(const std::string& username)
        {
            try
            {
                easymysql::executer(database::instance())("DELETE FROM users WHERE username = '{}'", username);

                bool ok = false;
                for (auto it = _users.begin(); it != _users.end(); ++it)
                {
                    if (it->username == username)
                    {
                        _users.erase(it);
                        ok = true;
                        break;
                    }
                }
                if (!ok)
                    throw model_except("user not found");
            }
            catch (const easymysql::except& e)
            {
                easylog::error("delete user failed exception : {}", e.what());
                throw model_except("delete user failed exception : " + std::string(e.what()));
                return false;
            }
            return true;
        }

        User* authenticate_user(const std::string& username, const std::string& password, const std::string& role)
        {
            User* user = find_user(username);
            if (user->password != password || user->role != role)
            {
                easylog::error("authenticate failed");
                throw model_except("authenticate failed");
            }

            return _current_user = user;
        }

        bool update_user(const std::string& username, const std::string& new_username, const std::string& new_password, const std::string& new_role)
        {
            try
            {
                std::string query("UPDATE users SET ");

                if (!new_username.empty()) query += "username = '" + new_username + "',";
                if (!new_password.empty()) query += "password = '" + new_password + "',";
                if (!new_role.empty())     query += "role = '" + new_role + "',";

                query.pop_back();
                query += " WHERE username = '" + username + "'";

                easymysql::executer(database::instance())(query);

                if (!database::instance().affected_rows())
                    throw easymysql::except("user not found");

                for (auto& user : _users)
                {
                    if (user.username == username)
                    {
                        if (!new_username.empty()) user.username = new_username;
                        if (!new_password.empty()) user.password = new_password;
                        if (!new_role.empty())     user.role = new_role;
                        break;
                    }
                }
            }
            catch (const easymysql::except& e)
            {
                easylog::error("update user failed exception: {}", e.what());
                throw model_except("update user failed exception: " + std::string(e.what()));
                return false;
            }
            return true;
        }

        User* get_user(const std::string& username)
        {
            User* user = find_user(username);
            return user;
        }

        User* current_user()
        {
            if (!_current_user)
                throw model_except("current user not found");

            return _current_user;
        }

        bool logout_user()
        {
            if (!_current_user)
                throw model_except("no user login");

            _current_user = nullptr;
            return true;
        }

    public:
        // No need to check the return pointer,
        // because it will throw exception if not found
        User* find_user(const std::string& username)
        {
            for (auto& user : _users)
            {
                if (user.username == username)
                    return &user;
            }

            throw model_except("user not found");
            return nullptr;
        }

    private:
        std::vector<User> _users;
        User* _current_user;
    };

}